package com.qfedu.edu.slot.impl;

import com.qfedu.edu.constance.RedisConstance;
import com.qfedu.edu.exception.handler.BusinessException;
import com.qfedu.edu.manager.RedisManager;
import com.qfedu.edu.result.ResponseCode;
import com.qfedu.edu.slot.generate.GenerateSlot;
import com.qfedu.edu.utils.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author xiaobobo
 * @title: LimitSlot
 * @projectName cd-fy-2401-third-project-parent
 * 需求:基于滑动时间窗口实现限流
 * 1、1分钟之内最多请求3次
 * 2、1小时之内最多请求100次
 * 3、1天之内最多请求300次
 * @description: TODO
 * @date 2024/9/23  15:34
 */
@Component
@Slf4j
public class LimitSlot extends GenerateSlot {

    @Resource
    private RedisManager redisManager;

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE + 1;
    }

    @Override
    public boolean dealReq(HttpServletRequest req) {
        log.info("限流检测....");
        //首先获取当前时间
        //一分钟之前的时间
        long minuteAgo = DateTime.now().minusMinutes(1).getMillis();
        //然后再计算这个1小时之前的时间
        long hourAgo = DateTime.now().minusHours(1).getMillis();
        //再计算这个1天之前的时间
        long dayAgo = DateTime.now().minusDays(1).getMillis();
        //获取当前时间
        long currentTime = DateTime.now().getMillis();
        //这里首先获取当前请求的ip地址
        String ipAddress= IpUtils.getIpAddress(req);
        // zcount 集合的名字 得分的开始时区间  得分的结束区间   使用这个命令对应的API
        //这里要调用zcount统计这个API
        //首先组合这个zSet集合的名字
        String zSetName= RedisConstance.getLimitPrefix()+ipAddress;
        if((redisManager.countZSet(zSetName,minuteAgo,currentTime))>30){
            log.info("超过了1分钟限流....");
            //我们认为是攻击
            redisManager.addString(RedisConstance.getBlackPrefix()+ipAddress, UUID.randomUUID().toString(),1, TimeUnit.DAYS);
            // black:ip地址  随机值   1天过期
            throw new BusinessException(ResponseCode.LIMIT_MINUTE_EXCEPTION.getCode(),
                    ResponseCode.LIMIT_MINUTE_EXCEPTION.getMsg());
        }else if((redisManager.countZSet(zSetName,hourAgo,currentTime))>100){
            log.info("超过了1小时限流....");
            //我们认为是攻击
            redisManager.addString(RedisConstance.getBlackPrefix()+ipAddress, UUID.randomUUID().toString(),1, TimeUnit.DAYS);
            throw new BusinessException(ResponseCode.LIMIT_HOUR_EXCEPTION.getCode(),
                    ResponseCode.LIMIT_HOUR_EXCEPTION.getMsg());
        }else if((redisManager.countZSet(zSetName,dayAgo,currentTime))>1000){
            log.info("超过了1天限流....");
            //我们认为是攻击
            redisManager.addString(RedisConstance.getBlackPrefix()+ipAddress, UUID.randomUUID().toString(),1, TimeUnit.DAYS);
            throw new BusinessException(ResponseCode.LIMIT_DAY_EXCEPTION.getCode(),
                    ResponseCode.LIMIT_DAY_EXCEPTION.getMsg());
        }
        //执行到这里 说明 在得分区间内的成员的个数 没有达到指定的阈值  那么就添加数据到 ZSet集合中
        //  zadd  limit:127.0.0.1  当前时间   随机值
        redisManager.addZSet(RedisConstance.getLimitPrefix()+ipAddress,UUID.randomUUID().toString(),currentTime);
        //这里使用Set再存储一次这个集合的名字 (也就是ZSet这个中集合的名字...)
        //这里之所以使用Set集合是因为每个用户我们只是记录一次这个名字 就可以 set集合恰好能去重
        String reqRecordName = RedisConstance.getReqRecordName();
        //使用RedisManager向set集合中添加数据
        redisManager.addSet(reqRecordName,RedisConstance.getLimitPrefix()+ipAddress);
        log.info("限流校验通过....");
        return true;
    }
}
